/**
 * 本地存储器
 * @author luguoxiang
 * @date 2022/9/17
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */

import { syncLockLoad } from '@/soar';

interface Loader {
  (): Promise<any> | any;
}

/**
 * 存储器配置
 */
interface Config {
  /**
   * 缓存名称，必填
   */
  name: string;
  /**
   * 缓存有效期，默认：-1
   */
  expires?: number;
  /**
   * 缓存加载器
   */
  loader?: Loader;
  /**
   * 浏览器存储器，localStorage || sessionStorage，默认：localStorage
   */
  storage?: Storage;
}

/**
 * 获取有效期
 * @param expires
 */
function getExpires(expires: number | Date) {
  if (!expires) {
    return -1;
  }
  if (typeof expires === 'number') {
    return expires > 0 ? expires * 1000 + new Date().getTime() : -1;
  }
  return expires.getTime();
}

/**
 * 存储器类
 */
export class SoarStorage {
  /**
   * key前缀
   * @private
   */
  private readonly name: string;

  /**
   * 默认有效期(单位：秒)
   * @private
   */
  private readonly expires: number = -1;

  /**
   * 默认加载器
   * @private
   */
  private readonly loader: Loader;

  /**
   * 存在器
   * @private
   */
  private readonly storage: Storage;

  /**
   * @param config 缓存名称，必填
   */
  constructor(config: Config | string) {
    if (typeof config === 'string') {
      // @ts-ignore
      config = { name: config };
    }
    // @ts-ignore
    this.name = config.name;
    // @ts-ignore
    this.expires = config.expires > 0 ? config.expires : -1;
    // @ts-ignore
    this.loader = config.loader;
    // @ts-ignore
    this.storage = config.storage ?? localStorage;
  }

  private getKey(key: string) {
    return this.name + key;
  }

  /**
   * 异步加载数据
   * @param key
   * @param loader 异步加载器
   * @param expires 有效期（单位：秒）
   * @return Promise<any>
   */
  load(key: string, loader: Loader = this.loader, expires: number | Date = this.expires): Promise<any> {
    const data = this.get(key);
    if (data === undefined || data === null) {
      return syncLockLoad(key, loader, (res: any) => {
        this.put(key, res, expires);
        return res;
      });
    }
    return Promise.resolve(data);
  }

  /**
   * 同步加载数据
   * @param key 键
   * @param supplier 数据提供器
   * @param expires 有效期（单位：秒）
   * @return any
   */
  public compute(key: string, supplier: Function, expires: number = this.expires): any {
    let data = this.get(key);
    if (data === undefined || data === null) {
      data = supplier(key);
      this.put(key, data, expires);
    }
    return data;
  }

  /**
   * 获取数据
   * @param key 键
   * @param defVal 默认值（无法获取值时返回）
   */
  get(key: string, defVal?: any): any {
    let data: any = this.storage.getItem(this.getKey(key));
    if (data) {
      try {
        data = JSON.parse(data);
        if (data && (data.e === -1 || data.e > new Date().getTime())) {
          return data.v;
        }
      } catch (e) {
        console.error(e);
      }
    }
    this.remove(key);
    return defVal;
  }

  /**
   * 保存数据
   * @param key 键
   * @param value 值
   * @param expires 有效期（毫秒：ms）
   */
  put(key: string, value: any, expires: number | Date = this.expires): void {
    key = this.getKey(key);
    const data = { v: value, e: getExpires(expires) };
    this.storage.setItem(key, JSON.stringify(data));
  }

  /**
   * 设置缓存有效期
   * @param key 键
   * @param expires 有效期（单位：秒）
   */
  public expire(key: string, expires?: number | Date): number {
    key = this.getKey(key);
    try {
      let data: any = this.storage.getItem(key);
      data = JSON.parse(data);
      if (expires) {
        data.e = getExpires(expires);
        this.storage.setItem(key, JSON.stringify(data));
      }
      return data.e;
    } catch (e) {
      console.error(e);
      return -2;
    }
  }

  /**
   * 删除
   * @param key 键
   */
  remove(...keys: string[]): void {
    keys.forEach((key) => {
      key = this.getKey(key);
      this.storage.removeItem(key);
    });
  }

  /**
   * 清空所有键
   */
  clear(): void {
    this.storage.clear();
  }
}

/**
 * 创建存储器
 * @param config 配置：
 *    name 缓存名称，必填
 *    expires 默认有效期，非必填
 *    loader 默认加载器，非必填
 *    storage 浏览器存储器，默认：LocalStorage
 */
export function createStorage(config: Config | string) {
  return new SoarStorage(config);
}

/**
 * 默认存储器实例
 */
export const storage = createStorage({ name: '$:' });
